﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Castle.Core.Interceptor;
using System.Linq.Expressions;

namespace ReAnimation.Helpers.DynamicProxy
{
    public class EventsSource: IEventsHandlersCollector
    {
        private object _eventsSource;
        private EventsSourceInterceptor _interceptor;
        private IList<MethodInfo> _afterMethods;
        private IList<MethodInfo> _beforeMethods;

        public static IEventsHandlersCollector EditSubscriptionsFor(object eventsSource)
        {
            if (eventsSource == null)
                throw new ArgumentNullException("eventsSource");

            var proxyTargetAccessor = eventsSource as IProxyTargetAccessor;
            if (proxyTargetAccessor == null)
                throw new ArgumentException("Must be proxied","eventsSource");//TODO: eliminate string constans through expression
            
            var eventsSourceInterceptor = proxyTargetAccessor.GetInterceptors().
                                                               Where(i => i is EventsSourceInterceptor).
                                                               Single() as EventsSourceInterceptor;

            return new EventsSource(eventsSource,eventsSourceInterceptor);
        }

        public IEventsHandlersCollector AfterPropertySet<T>(Expression<Func<T>> getPropertyExpression)
        {
            _afterMethods.Add(ExtractPropertySetterMethod(getPropertyExpression));
            return this;
        }

        public IEventsHandlersCollector BeforePropertySet<T>(Expression<Func<T>> getPropertyExpression)
        {
            _beforeMethods.Add(ExtractPropertySetterMethod(getPropertyExpression));
            return this;
        }

        public IEventsHandlersCollector AfterMethodCall<T>(Expression<Action<T>> methodCallExpression)
        {
            _afterMethods.Add(ExtractMethodCall(methodCallExpression));
            return this;
        }

        public IEventsHandlersCollector BeforeMethodCall<T>(Expression<Action<T>> methodCallExpression)
        {
            _beforeMethods.Add(ExtractMethodCall(methodCallExpression));
            return this;
        }

        public void Handler(Action handler)
        {
            _interceptor.AddAfterMethodCallHandler(_afterMethods, handler);
            _interceptor.AddBeforeMethodCallHandler(_beforeMethods, handler);
        }

        public void RemoveHandler(Action handler)
        {
            _interceptor.RemoveHandler(handler);
        }

        public void ClearSubscriptions()
        {
            _interceptor.ClearSubscriptions();
        }

        private static MethodInfo ExtractMethodCall<T>(Expression<Action<T>> exp)
        {
            MethodCallExpression methodCallExp = exp.Body as MethodCallExpression;

            if (methodCallExp == null) throw new ArgumentException("Expression must be a method call", "exp");

            return methodCallExp.Method;
        }

        private static MethodInfo ExtractPropertySetterMethod<T>(Expression<Func<T>> exp)
        {
            MemberExpression memberExp = exp.Body as MemberExpression;

            if (memberExp == null) throw new ArgumentException("Expression must contain object getter call","exp");

            PropertyInfo propertyInfo = memberExp.Member as PropertyInfo;

            MethodInfo methodInfo = propertyInfo.GetSetMethod();
            return methodInfo;
        }

        private EventsSource(object eventsSource,EventsSourceInterceptor interceptor)
        {
            _eventsSource = eventsSource;
            _interceptor = interceptor;

            _afterMethods = new List<MethodInfo>();
            _beforeMethods = new List<MethodInfo>();
        }
    }
}
